package com.qinggeng.project.tool.pay.service.impl;

import com.alibaba.fastjson.JSON;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConfig;
import com.github.wxpay.sdk.WXPayUtil;
import com.google.common.collect.Maps;
import com.qinggeng.common.constant.Constants;
import com.qinggeng.common.enums.PayStatusEnum;
import com.qinggeng.common.enums.TradeStatusEnum;
import com.qinggeng.common.exception.CustomException;
import com.qinggeng.common.utils.DateUtils;
import com.qinggeng.common.utils.ServletUtils;
import com.qinggeng.common.utils.StringUtils;
import com.qinggeng.common.utils.ip.IpUtils;
import com.qinggeng.project.api.security.utils.ApiUtils;
import com.qinggeng.project.system.domain.Orders;
import com.qinggeng.project.system.service.IOrdersService;
import com.qinggeng.project.system.service.ISysConfigService;
import com.qinggeng.project.system.service.MQSendService;
import com.qinggeng.project.tool.im.util.HttpUtils;
import com.qinggeng.project.tool.im.util.SignCreateUtil;
import com.qinggeng.project.tool.im.util.WxToolUtil;
import com.qinggeng.project.tool.im.util.XMLUtil;
import com.qinggeng.project.tool.pay.domain.PayUrl;
import com.qinggeng.project.tool.pay.domain.PayWxpay;
import com.qinggeng.project.tool.pay.domain.WxWithdrawModal;
import com.qinggeng.project.tool.pay.domain.json.WxpayConfigJson;
import com.qinggeng.project.tool.pay.domain.vo.TradeVo;
import com.qinggeng.project.tool.pay.enums.PayWayEnum;
import com.qinggeng.project.tool.pay.service.IPayWxpayService;
import com.qinggeng.project.tool.pay.service.WxPayService;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.util.EntityUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class WxPayServiceImpl implements WxPayService {

    @Autowired
    private ISysConfigService configService;


    @Autowired
    private IPayWxpayService payWxpayService;

    @Autowired
    private IOrdersService ordersService;

    private WXPayConfig config;

    private WxpayConfigJson configObject;

    @Resource
    private RedissonClient  redissonClient;

    @Autowired
    private RedisTemplate  redisTemplate;

    @Resource
    private MQSendService mqSendService;

    @Value("${wxpay.certPath}")
    private String certPath;


    private WXPay getWxPay(PayWayEnum wayEnum){
        String fileConfigJson = configService.selectConfigByKey("sys.pay.wxpay").toString();
        configObject = JSON.parseObject(fileConfigJson, WxpayConfigJson.class);
        config = new WXPayConfig() {
            @Override
            public String getAppID() {
                switch (wayEnum){
                    case WEB:
                    case SCAN:
                        return configObject.getWebAppId();
                    case WAP:
                    case MP:
                        return configObject.getH5AppId();
                    case APP:
                        return configObject.getAppId();
                    case MINIAPP:
                        return configObject.getMiniappId();
                }
                return configObject.getAppId();
            }

            @Override
            public String getMchID() {
                return configObject.getMchId();
            }

            @Override
            public String getKey() {
                return configObject.getKey();
            }

            @Override
            public InputStream getCertStream() {
                try {
                        File file = new File(certPath);
                    InputStream certStream = new FileInputStream(file);
                    byte[] certData = new byte[(int) file.length()];
                    certStream.read(certData);
                    certStream.close();
                    return new ByteArrayInputStream(certData);
                }catch (Exception e){
                    throw new CustomException("读取微信支付证书异常");
                }
            }

            @Override
            public int getHttpConnectTimeoutMs() {
                return 2000;
            }

            @Override
            public int getHttpReadTimeoutMs() {
                return 10000;
            }
        };
        return new WXPay(config);
    }

    @Override
    public String toPayAsWeb(TradeVo trade) throws Exception {
        WXPay wxPay = getWxPay(PayWayEnum.WEB);
        Map<String, String> orderResult = unifiedOrder(wxPay, trade, "NATIVE");
        if (!orderResult.get("return_code").equals("SUCCESS")){
            throw new CustomException("下单异常");
        }
        return null;
    }

    @Override
    public String toPayAsWap(TradeVo trade) throws Exception {
        WXPay wxPay = getWxPay(PayWayEnum.WAP);
        Map<String, String> orderResult = unifiedOrder(wxPay, trade, "MWEB");
        if (!orderResult.get("return_code").equals("SUCCESS")){
            throw new CustomException("下单异常");
        }

        // 调用SDK生成表单, 通过GET方式，口可以获取url
        return null;
    }

    @Override
    public String toPayAsApp(TradeVo trade) throws Exception {
        WXPay wxPay = getWxPay(PayWayEnum.APP);
        Map<String, String> orderResult = unifiedOrder(wxPay, trade, "APP");
        if (!orderResult.get("return_code").equals("SUCCESS")){
            throw new CustomException("下单异常："+orderResult.get("return_msg"));
        }
        Map<String,String> payRequest = Maps.newHashMap();
        payRequest.put("appid",orderResult.get("appid"));
        payRequest.put("partnerid",orderResult.get("mch_id"));
        payRequest.put("prepayid",orderResult.get("prepay_id"));
        payRequest.put("package","Sign=WXPay");
        payRequest.put("noncestr", orderResult.get("nonce_str"));
        payRequest.put("timestamp",System.currentTimeMillis() / 1000 + "");
        payRequest.put("sign",WXPayUtil.generateSignature(new TreeMap<>(payRequest),configObject.getKey()));
        // 调用SDK生成表单, 通过GET方式，口可以获取url
        return JSON.toJSONString(payRequest);
    }

    @Override
    public String toPayAsScan(TradeVo trade) throws Exception {
        WXPay wxPay = getWxPay(PayWayEnum.SCAN);
        Map<String, String> orderResult = unifiedOrder(wxPay, trade, "NATIVE");
        if (!orderResult.get("return_code").equals("SUCCESS")){
            throw new CustomException("下单异常");
        }
        //获取二维码URL
        return orderResult.get("code_url");
    }

    @Override
    public String toPayAsMp(TradeVo trade) throws Exception {
        WXPay wxPay = getWxPay(PayWayEnum.MP);
        Map<String, String> orderResult = unifiedOrder(wxPay, trade, "JSAPI");
        if (!orderResult.get("return_code").equals("SUCCESS")){
            throw new CustomException("下单异常");
        }
        return null;
    }

    @Override
    public String toPayAsMiniapp(TradeVo trade) throws Exception {
        WXPay wxPay = getWxPay(PayWayEnum.MINIAPP);
        Map<String, String> orderResult = unifiedOrder(wxPay, trade, "JSAPI");
        if (!orderResult.get("return_code").equals("SUCCESS")){
            throw new CustomException("下单异常");
        }
        Map<String,String> payRequest = Maps.newHashMap();
        payRequest.put("appId",orderResult.get("appid"));
        payRequest.put("timeStamp",System.currentTimeMillis() / 1000 + "");
        payRequest.put("nonceStr", orderResult.get("nonce_str"));
        payRequest.put("package","prepay_id=" +orderResult.get("prepay_id"));
        payRequest.put("signType","MD5");
        payRequest.put("paySign",WXPayUtil.generateSignature(payRequest,configObject.getKey()));
        // 调用SDK生成表单, 通过GET方式，口可以获取url
        return JSON.toJSONString(payRequest);
    }


    /**
     * 下单
     * @param wxPay 支付Sdk
     * @param trade 交易详情
     * @param tradeType JSAPI -JSAPI支付 NATIVE -Native支付 APP -APP支付
     * @return /
     */
    private Map<String,String> unifiedOrder(WXPay wxPay,TradeVo trade,String tradeType){
        try {
            String fileConfigJson = configService.selectConfigByKey("sys.pay.wxpay").toString();
            WxpayConfigJson configObject = JSON.parseObject(fileConfigJson, WxpayConfigJson.class);

            Map<String, String> data = Maps.newHashMap();
            data.put("body", trade.getBody());
            data.put("out_trade_no", trade.getOutTradeNo());
//            data.put("device_info", "");
            data.put("fee_type", "CNY");
            data.put("total_fee", trade.getTotalAmount().multiply(new BigDecimal(100)).intValue() + "");
            data.put("spbill_create_ip", IpUtils.getIpAddr(ServletUtils.getRequest()));
            data.put("notify_url", configObject.getNotifyUrl());
            data.put("trade_type", tradeType);
            if (StringUtils.isNotEmpty(trade.getOpenid())){
                data.put("openid",trade.getOpenid());
            }
//            data.put("sub_mch_id",configObject.getMchId());
//            data.put("product_id", "12");
            Map<String, String> orderResult = wxPay.unifiedOrder(data);
            System.out.println(orderResult);
            log.info(orderResult.toString());
            PayWxpay payWxpay = payWxpayService.selectPayWxpayByOutTradeNo(trade.getOutTradeNo());
            if (payWxpay == null){
                payWxpay = new PayWxpay();
                payWxpay.setOutTradeNo(trade.getOutTradeNo());
                payWxpay.setTradeStatus(TradeStatusEnum.等待付款.getValue());
                payWxpay.setBody(trade.getSubject());
                payWxpay.setDetail(trade.getBody());
                payWxpay.setTradeType(tradeType);
                payWxpay.setGmtCreate(DateUtils.getNowDate());
                payWxpay.setTotalFee(trade.getTotalAmount().multiply(new BigDecimal(100)).intValue());
                payWxpayService.insertPayWxpay(payWxpay);
            }

            return orderResult;
        }catch (Exception e){
            throw new CustomException("下单异常："+e.getMessage());
        }
    }

    @Override
    public boolean rsaCheck(Map<String, String> rsa) {
        try {
            return WXPayUtil.isSignatureValid(rsa,getConfig().getKey());
        }catch (Exception e){
            throw new CustomException("验证签名异常");
        }
    }
    @Resource
    private DataSourceTransactionManager dataSourceTransactionManager;

    @Resource
    private TransactionDefinition transactionDefinition;


    @Override
    public boolean notifyCallback(Map<String, String> parameterMap) {
        RLock redisLock =  redissonClient.getLock("NOTIFY_ORDERS_" + parameterMap.get("out_trade_no"));
        redisLock.lock();
        TransactionStatus transactionStatus=null;
        try {
            transactionStatus=dataSourceTransactionManager.getTransaction(transactionDefinition);
            PayWxpay payWxpay = payWxpayService.selectPayWxpayByOutTradeNo(parameterMap.get("out_trade_no"));
            if (payWxpay.getTradeStatus().equals(TradeStatusEnum.支付成功.getValue())){
                return true;
            }

            String orderNo=parameterMap.get("out_trade_no");
            ordersService.notifyCallback(orderNo,"wxpay");
            payWxpay.setTransactionId(parameterMap.get("transaction_id"));
            payWxpay.setOpenid(parameterMap.get("openid"));
            payWxpay.setGmtPayment(DateUtils.getNowDate());
            payWxpay.setTradeStatus(TradeStatusEnum.支付成功.getValue());
            int size=payWxpayService.updatePayWxpay(payWxpay);
            dataSourceTransactionManager.commit(transactionStatus);
            if (size>=1){
                log.info("支付成功回调"+orderNo);
                //发送限购商品购买记录消息
                mqSendService.sendUserGoodsLimitAsynMessage(orderNo);
                //发送自动接单异步消息
                mqSendService.sendOrdersAutoTakeAsynMessage(orderNo);
                //发送订单打印状态延时消息
                mqSendService.sendPrintStatusDelayMessage(orderNo);
                //发送自动发货延时消息
                //mqSendService.sendOrdersAutoShipDelayMessage(orderNo);
            }
        } catch (Exception e) {
            dataSourceTransactionManager.rollback(transactionStatus);
            return false;
        } finally {
            redisLock.unlock();
        }
        return true;
    }

    @Override
    public WxpayConfigJson getConfig() {
        String fileConfigJson = configService.selectConfigByKey("sys.pay.wxpay").toString();
        return JSON.parseObject(fileConfigJson, WxpayConfigJson.class);
    }

    @Override
    public void refund(String ordersNo, BigDecimal refundAmount) throws Exception {
        log.info("微信退款{}",ordersNo);
        WXPay wxpay = getWxPay(PayWayEnum.APP);

        String fileConfigJson = configService.selectConfigByKey("sys.pay.wxpay").toString();
        WxpayConfigJson configObject = JSON.parseObject(fileConfigJson, WxpayConfigJson.class);

        PayWxpay payWxpay = payWxpayService.selectPayWxpayByOutTradeNo(ordersNo);
        //多商品退款需要
        if (payWxpay.getTradeStatus().equals(TradeStatusEnum.已退款.getValue())){
            //已退款成功的直接返回正常
            return;
        }
        if (!payWxpay.getTradeStatus().equals(TradeStatusEnum.支付成功.getValue())){
            throw new CustomException("订单状态异常无法退款:"+ordersNo);
        }
        if (Constants.DEBUG){
            refundAmount = new BigDecimal("0.01");
        }
        log.info("------微信退款支付逻辑开始--------");
        Map<String, String> refunMap = new TreeMap<>();
        refunMap.put("appid",config.getAppID());//公众账号ID
        refunMap.put("mch_id",config.getMchID());//商户号
        refunMap.put("nonce_str", StringUtils.createNoncestr(32));//随机字符串
        refunMap.put("out_trade_no",ordersNo);//商户订单号
        refunMap.put("out_refund_no",ordersNo);//商户退款单号
        refunMap.put("total_fee",payWxpay.getTotalFee() + "");//订单金额
        refunMap.put("refund_fee",(refundAmount.multiply(new BigDecimal(100)).intValue()) + "");//退款金额
        refunMap.put("sign",WXPayUtil.generateSignature(new TreeMap<>(refunMap),configObject.getKey()));// 签名，需要将前面的参数加密生成
        Map<String, String> result = wxpay.refund(refunMap);
        log.info("信息:"+result.toString());
        log.info("------微信退款支付逻辑结束--------");
        if (result.get("return_code").equals("FAIL")){
            throw new CustomException("退款失败");
        }else{

            payWxpay.setTradeStatus(TradeStatusEnum.已退款.getValue());
            payWxpay.setGmtRefund(DateUtils.getNowDate());
            payWxpay.setGmtClose(DateUtils.getNowDate());
            payWxpayService.updatePayWxpay(payWxpay);
        }
    }


    public String refund(String ordersNo, Long ordersGoodsId,BigDecimal refundAmount) throws Exception {
        log.info("微信退款{}",ordersGoodsId);
        WXPay wxpay = getWxPay(PayWayEnum.APP);

        String fileConfigJson = configService.selectConfigByKey("sys.pay.wxpay").toString();
        WxpayConfigJson configObject = JSON.parseObject(fileConfigJson, WxpayConfigJson.class);

        PayWxpay payWxpay = payWxpayService.selectPayWxpayByOutTradeNo(ordersNo);

        if (Constants.DEBUG){
            refundAmount = new BigDecimal("0.01");
        }
        log.info("------微信退款支付逻辑开始--------");
        Map<String, String> refunMap = new TreeMap<>();
        refunMap.put("appid",config.getAppID());//公众账号ID
        refunMap.put("mch_id",config.getMchID());//商户号
        refunMap.put("nonce_str", StringUtils.createNoncestr(32));//随机字符串
        refunMap.put("out_trade_no",ordersNo);//商户订单号
        refunMap.put("out_refund_no",ordersNo+"-"+ordersGoodsId);//商户退款单号
        refunMap.put("total_fee",payWxpay.getTotalFee() + "");//订单金额
        refunMap.put("refund_fee",(refundAmount.multiply(new BigDecimal(100)).intValue()) + "");//退款金额
        refunMap.put("sign",WXPayUtil.generateSignature(new TreeMap<>(refunMap),configObject.getKey()));// 签名，需要将前面的参数加密生成
        Map<String, String> result = wxpay.refund(refunMap);
        log.info("信息:"+result.toString());
        log.info("------微信退款支付逻辑结束--------");
        if (result.get("return_code").equals("FAIL")){
            throw new CustomException("退款失败");
        }
        return  result.toString();
    }


    //记录每天用户下单的次数
    private void userOrdersCount(){
        Boolean flag = redisTemplate.opsForValue().setIfAbsent("USER_ID" + ApiUtils.getUserId(), 1, DateUtils.getSeconds(), TimeUnit.SECONDS);
        if (flag !=null){
            if (!flag){
                redisTemplate.opsForValue().increment("USER_ID" + ApiUtils.getUserId());
            }
        }
        log.info(ApiUtils.getApiUser().getUsername()+":第"+redisTemplate.opsForValue().get("USER_ID" + ApiUtils.getUserId())+"单");
    }

    @Override
    public boolean withdrawal(WxWithdrawModal modal) {
        //随机字符串
        String nonce_str = WxToolUtil.getNonce_str();
        //ip
        //String spbill_create_ip = WxToolUtil.getIpAddress();
        //微信是以分为单位
        String money = new BigDecimal(modal.getMoney()).multiply(new BigDecimal("100")).intValue() + "";
        //参数：开始生成签名
        SortedMap<String,Object> parameters = new TreeMap<String,Object>();
        parameters.put("mch_appid", modal.getAppid());
        parameters.put("mchid", modal.getMchId());
        parameters.put("nonce_str", nonce_str);
        parameters.put("partner_trade_no", modal.getTradeNo());
        parameters.put("openid", modal.getOpenid());
        parameters.put("check_name", "NO_CHECK");
//        parameters.put("spbill_create_ip", spbill_create_ip);
        parameters.put("amount", money);
        parameters.put("desc", modal.getDesc());
        // 签名
        String sign = SignCreateUtil.createSign(modal.getSecurity(), parameters);
        // 拼接请求参数
        parameters.put("sign", sign);
        // 转换请求参数为XML
        String xml = XMLUtil.parseXML(parameters);
        try {
            CloseableHttpResponse response =  HttpUtils.Post(PayUrl.WITHDRAW_URL, xml,modal.getMchId(), modal.getCertUrl(),true);
            String transfersXml = EntityUtils.toString(response.getEntity(), "utf-8");
            log.info("返回的xml为：{}",transfersXml);
            Map<String, String> transferMap = XMLUtil.parseMap(transfersXml);
            if (transferMap.size()>0) {
                if (transferMap.get("result_code").equals("SUCCESS") && transferMap.get("return_code").equals("SUCCESS")) {
                    return true;
                }
            }
        } catch (Exception e) {
            throw new CustomException("提现"+e.getMessage());

        }
        throw new CustomException("提现异常");
    }

    @Override
    public String paymentOnBehalfOf(TradeVo tradeVo) throws Exception {
        String ordersNo= tradeVo.getOutTradeNo();
        Orders orders= ordersService.selectOrdersByOrdersNo(ordersNo);
        if (orders==null){
            throw  new CustomException("订单不存在");
        }
        if (orders.getPayStatus().equals(PayStatusEnum.已支付.getValue())){
            throw  new CustomException("订单已支付");
        }
        if (orders.getPayStatus().equals(PayStatusEnum.已超时.getValue())){
            throw  new CustomException("订单已超时");
        }
        WXPay wxPay = getWxPay(PayWayEnum.MINIAPP);
        Map<String, String> orderResult = unifiedOrder(wxPay, tradeVo, "JSAPI");
        if (!orderResult.get("return_code").equals("SUCCESS")){
            throw new CustomException("下单异常");
        }
        Map<String,String> payRequest = Maps.newHashMap();
        payRequest.put("appId",orderResult.get("appid"));
        payRequest.put("timeStamp",System.currentTimeMillis() / 1000 + "");
        payRequest.put("nonceStr", orderResult.get("nonce_str"));
        payRequest.put("package","prepay_id=" +orderResult.get("prepay_id"));
        payRequest.put("signType","MD5");
        payRequest.put("paySign",WXPayUtil.generateSignature(payRequest,configObject.getKey()));
        //代付流程 把订单修改为80
        log.info("{} 代付把订单状态修改为80",ordersNo);
        ordersService.updatePaymentOnBehalfOfType(ordersNo);
        // 调用SDK生成表单, 通过GET方式，口可以获取url
        return JSON.toJSONString(payRequest);

    }

}
